<!DOCTYPE html>
<title>Service Worker: UseCounter</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helpers.js"></script>
<script>
// Dummy values for features.
const kFeature = 675;
const kDeprecatedFeature = 166;

function isUseCounted(win, feature) {
  return win.internals.isUseCounted(win.document, feature);
}

function observeUseCounter(win, feature) {
  return win.internals.observeUseCounter(win.document, feature);
}

// Use a window instead of an iframe because UseCounter is shared among frames
// in a document and these tests cannot be conducted in such an environment.
// A window has its own UseCounter.
function openWindow(url) {
  return new Promise(resolve => {
      const win = window.open(url, '_blank');
      add_completion_callback(() => win.close());
      window.onmessage = e => {
        assert_equals(e.data, 'LOADED');
        resolve(win);
      };
    });
}

promise_test(async t => {
  const kUrl = 'resources/usecounter-worker.js';
  const kScope = 'resources/usecounter-window.html?basic';

  const registration =
      await service_worker_unregister_and_register(t, kUrl, kScope);
  t.add_cleanup(() => registration.unregister());
  const worker = registration.installing;
  await wait_for_state(t, registration.installing, 'activated');
  const win1 = await openWindow(kScope);
  const win2 = await openWindow(kScope);
  assert_false(isUseCounted(win1, kFeature));
  assert_false(isUseCounted(win2, kFeature));

  // Request to count a feature.
  worker.postMessage({ type: 'COUNT_FEATURE', feature: kFeature });
  await observeUseCounter(win1, kFeature);
  await observeUseCounter(win2, kFeature);

  // API use on ServiceWorkerGlobalScope should be recorded in all controlled
  // windows.
  assert_true(isUseCounted(win1, kFeature));
  assert_true(isUseCounted(win2, kFeature));

  assert_false(isUseCounted(win1, kDeprecatedFeature));
  assert_false(isUseCounted(win2, kDeprecatedFeature));

  // Request to count a deprecated feature.
  worker.postMessage(
      { type: 'COUNT_DEPRECATION', feature: kDeprecatedFeature });
  await observeUseCounter(win1, kDeprecatedFeature);
  await observeUseCounter(win2, kDeprecatedFeature);

  // Deprecated API use on ServiceWorkerGlobalScope should be recorded in all
  // controlled windows.
  assert_true(isUseCounted(win1, kDeprecatedFeature));
  assert_true(isUseCounted(win2, kDeprecatedFeature));

  // Check UseCounters have been sent to the new window. Since this can happen
  // after the new window's document load, an observer is used to wait until it
  // happens.
  const win = await openWindow(kScope);
  await observeUseCounter(win, kFeature);
  await observeUseCounter(win, kDeprecatedFeature);
}, 'UseCounter on ServiceWorkerGlobalScope');

promise_test(async t => {
  const kUrl = 'resources/usecounter-worker.js';
  const kScope = 'resources/usecounter-window.html?claim';

  const win1 = await openWindow(kScope);
  const win2 = await openWindow(kScope);
  const registration =
      await service_worker_unregister_and_register(t, kUrl, kScope);
  t.add_cleanup(() => registration.unregister());
  const worker = registration.installing;
  await wait_for_state(t, registration.installing, 'activated');

  // Request to count a feature.
  worker.postMessage({type: 'COUNT_FEATURE', feature: kFeature});
  let msgEvent = await new Promise(resolve => {
    navigator.serviceWorker.onmessage = resolve;
    // There is no way to verify that API use is never counted. As a workaround,
    // wait for only one round-trip.
    worker.postMessage({type: 'PING'});
  });
  assert_equals(msgEvent.data.type, 'PONG');

  // API use on ServiceWorkerGlobalScope should not be recorded in windows
  // because they are not controlled yet.
  assert_false(isUseCounted(win1, kFeature));
  assert_false(isUseCounted(win2, kFeature));

  // Request to count a deprecated feature.
  worker.postMessage(
      { type: 'COUNT_DEPRECATION', feature: kDeprecatedFeature });
  msgEvent = await new Promise(resolve => {
    navigator.serviceWorker.onmessage = resolve;
    // There is no way to verify that API use is never counted. As a workaround,
    // wait for only one round-trip.
    worker.postMessage({type: 'PING'});
  });
  assert_equals(msgEvent.data.type, 'PONG');

  // Deprecated API use on ServiceWorkerGlobalScope should not be recorded in
  // windows because they are not controlled yet.
  assert_false(isUseCounted(win1, kDeprecatedFeature));
  assert_false(isUseCounted(win2, kDeprecatedFeature));

  assert_equals(win1.navigator.serviceWorker.controller, null);
  assert_equals(win2.navigator.serviceWorker.controller, null);

  // Request to claim.
  msgEvent = await new Promise(resolve => {
    navigator.serviceWorker.onmessage = resolve;
    worker.postMessage({type: 'CLAIM'});
  });

  assert_equals(msgEvent.data.type, 'CLAIMED');
  assert_false(msgEvent.data.restarted);
  assert_not_equals(win1.navigator.serviceWorker.controller, null);
  assert_not_equals(win2.navigator.serviceWorker.controller, null);

  // The windows are now controlled by the service worker. Their UseCounter
  // should be synchronized with worker's counter.
  assert_true(isUseCounted(win1, kFeature));
  assert_true(isUseCounted(win2, kFeature));
  assert_true(isUseCounted(win1, kDeprecatedFeature));
  assert_true(isUseCounted(win2, kDeprecatedFeature));
}, 'UseCounter on ServiceWorkerGlobalScope - A use counter owned by newly ' +
   'controlled window should be synchronized with worker\'s counter');

// Test that features used during service worker installation are persisted.
// This test could be non-deterministic because there is no handy way to sweep
// out on-memory representation of ServiceWorker in the browser process and make
// sure to restore it from the storage.
promise_test(async t => {
  const kUrl = 'resources/usecounter-worker.js';
  const kScope = 'resources/usecounter-window.html' +
                 '?type=features-during-install' +
                 '&feature=' + kFeature +
                 '&deprecated=' + kDeprecatedFeature;

  const win1 = await openWindow(kScope);
  const win2 = await openWindow(kScope);

  // A service worker will call some APIs during the install event.
  const registration =
      await service_worker_unregister_and_register(t, kUrl, kScope);
  t.add_cleanup(() => registration.unregister());
  const worker = registration.installing;
  await wait_for_state(t, registration.installing, 'activated');

  assert_equals(win1.navigator.serviceWorker.controller, null);
  assert_equals(win2.navigator.serviceWorker.controller, null);

  // API use on ServiceWorkerGlobalScope should not be recorded in windows
  // because they are not controlled yet.
  assert_false(isUseCounted(win1, kFeature));
  assert_false(isUseCounted(win2, kFeature));
  assert_false(isUseCounted(win1, kDeprecatedFeature));
  assert_false(isUseCounted(win2, kDeprecatedFeature));

  // Terminate the service worker.
  await internals.terminateServiceWorker(worker);

  // Request to claim. This will restart the service worker.
  const msgEvent = await new Promise(resolve => {
    navigator.serviceWorker.onmessage = resolve;
    worker.postMessage({type: 'CLAIM'});
  });

  assert_equals(msgEvent.data.type, 'CLAIMED');
  assert_true(msgEvent.data.restarted);
  assert_not_equals(win1.navigator.serviceWorker.controller, null);
  assert_not_equals(win2.navigator.serviceWorker.controller, null);

  // The windows are now controlled by the service worker. Their UseCounter
  // should be synchronized with worker's counter retrieved from the storage.
  assert_true(isUseCounted(win1, kFeature));
  assert_true(isUseCounted(win2, kFeature));
  assert_true(isUseCounted(win1, kDeprecatedFeature));
  assert_true(isUseCounted(win2, kDeprecatedFeature));
}, 'UseCounter on ServiceWorkerGlobalScope - counts during the install ' +
   'event should be persisted');

// TODO(nhiroki): Test that features used after service worker installation are
// not persisted. This could be impossible because there is no handy way to
// sweep out on-memory representation of ServiceWorker in the browser process
// and make sure to restore it from the storage.

promise_test(async t => {
  const kUrl = 'resources/usecounter-worker.js';
  const kScope = 'resources/usecounter-window.html?type=skip-waiting';

  const registration1 =
      await service_worker_unregister_and_register(t, kUrl, kScope);
  t.add_cleanup(() => registration1.unregister());
  const worker1 = registration1.installing;
  await wait_for_state(t, registration1.installing, 'activated');
  const win1 = await openWindow(kScope);
  assert_false(isUseCounted(win1, kFeature));

  // Request to count a feature.
  worker1.postMessage({type: 'COUNT_FEATURE', feature: kFeature});
  await observeUseCounter(win1, kFeature);

  // API use on ServiceWorkerGlobalScope should be recorded in a controlled
  // window.
  assert_true(isUseCounted(win1, kFeature));

  // Update a controller using skipWaiting().
  const registration2 = await navigator.serviceWorker.register(
      kUrl + '?skip-waiting', {scope: kScope});
  t.add_cleanup(() => registration2.unregister());
  const worker2 = registration2.installing;

  // Wait until the new worker gets activated.
  await wait_for_state(t, worker2, 'activated');
  const win2 = await openWindow(kScope);

  // This window wasn't controlled by the previous worker.
  assert_not_equals(win2.navigator.serviceWorker.controller, undefined);

  // An updated worker does not take over the previous counter, so API use on
  // the previous worker should not be recorded in the newly controlled window.
  assert_true(isUseCounted(win1, kFeature));
  assert_false(isUseCounted(win2, kFeature));

  assert_false(isUseCounted(win1, kDeprecatedFeature));
  assert_false(isUseCounted(win2, kDeprecatedFeature));

  // Request to count a deprecated feature.
  worker2.postMessage(
      { type: 'COUNT_DEPRECATION', feature: kDeprecatedFeature });
  await observeUseCounter(win1, kDeprecatedFeature);
  await observeUseCounter(win2, kDeprecatedFeature);

  // Deprecated API use on the updated worker should be recorded in all
  // controlled windows.
  assert_true(isUseCounted(win1, kFeature));
  assert_false(isUseCounted(win2, kFeature));
  assert_true(isUseCounted(win1, kDeprecatedFeature));
  assert_true(isUseCounted(win2, kDeprecatedFeature));
}, 'UseCounter on ServiceWorkerGlobalScope - an updated worker should not ' +
   'take over a previous counter');

promise_test(async t => {
  const kFetchEventIsReload = 2032;  // from web_feature.mojom
  const kUrl = 'resources/use-isReload-worker.js';
  const kScope = 'resources/usecounter-window.html?isReload';

  const registration =
      await service_worker_unregister_and_register(t, kUrl, kScope);
  add_result_callback(() => registration.unregister());
  await wait_for_state(t, registration.installing, 'activated');

  const win = await openWindow(kScope);
  await observeUseCounter(win, kFetchEventIsReload);
  assert_true(isUseCounted(win, kFetchEventIsReload));
}, 'FetchEvent.isReload is counted');

promise_test(async t => {
  const kServiceWorkerFrameType = 2033;  // from web_feature.mojom
  const kUrl = 'resources/feature-worker.js';
  const kScope = 'resources/usecounter-window.html?frameType';

  const registration =
      await service_worker_unregister_and_register(t, kUrl, kScope);
  add_result_callback(() => registration.unregister());
  const worker = registration.installing;
  await wait_for_state(t, worker, 'activated');

  const win = await openWindow(kScope);
  worker.postMessage('use-frameType');
  await observeUseCounter(win, kServiceWorkerFrameType);
  assert_true(isUseCounted(win, kServiceWorkerFrameType));
}, 'Client.frameType is counted');

// TODO(nhiroki): Test a case where ServiceWorker controls SharedWorker that is
// connected from multiple windows. In such a case, API use on ServiceWorker
// should be propagated to all connecting windows via SharedWorker.

</script>
</html>
